第一章 V4L2简介 ==================== 1.1、什么是v4l2 +++++++++++++++++++ V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。 V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备: * **Video capture device** : 从摄像头等设备上获取视频数据。对很多人来讲,video capture是V4L2的基本应用。设备名称为/dev/video,主设备号81,子设备号0~63 * **Video output device** : 将视频数据编码为模拟信号输出。与video capture设备名相同。 * **Video overlay device** : 将同步锁相视频数据(如TV)转换为VGA信号,或者将抓取的视频数据直接存放到视频卡的显存中。 * **Video output overlay device** :也被称为OSD(On-Screen Display) * **VBI device** : 提供对VBI(Vertical Blanking Interval)数据的控制,发送VBI数据或抓取VBI数据。设备名/dev/vbi0~vbi31,主设备号81,子设备号224~255 * **Radio device** : FM/AM发送和接收设备。设备名/dev/radio0~radio63,主设备号81,子设备号64~127 V4L2在Linux系统中的结构图如下: .. _fig_v4l2_arch: .. figure:: images/V4L2框图.png V4L2简单框图 1.2、从应用层看V4L2 +++++++++++++++++++++++++ 从\ :ref:`fig_v4l2_arch`\ 可以看出,V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。 **可以将这些ioctl分类如下**: 1. Query Capability:查询设备支持的功能,只有VIDIOC_QUERY_CAP一个。 2. 优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。 3. capture相关:视频捕获相关Ioctl。 .. csv-table:: **capture ioctl list** :header: ID,描述 :widths: 20,150 :quote: $ :delim: | VIDIOC_ENUM_FMT | 枚举设备所支持的所有数据格式 VIDIOC_S_FMT | 设置数据格式 VIDIOC_G_FMT | 获取数据格式 VIDIOC_TRY_FMT | 与VIDIOC_S_FMT一样,但不会改变设备的状态 VIDIOC_REQBUFS | 向设备请求视频缓冲区,即初始化视频缓冲区 VIDIOC_QUERYBUF | 查询缓冲区的状态 VIDIOC_QBUF | 从设备获取一帧视频数据 VIDIOC_DQBUF | 将视频缓冲区归回给设备, VIDIOC_OVERLAY | 开始或者停止overlay VIDIOC_G_FBUF | 获取video overlay设备或OSD设备的framebuffer参数 VIDIOC_S_FBUF | 设置framebuffer参数 VIDIOC_STREAMON | 开始流I/O操作,capture or output device VIDIOC_STREAMOFF | 关闭流I/O操作 4. TV视频标准: .. csv-table:: **TV Standard** :header: ID,描述 :widths: 20,150 :quote: $ :delim: | VIDIOC_ENUMSTD | 枚举设备支持的所有标准 VIDIOC_G_STD | 获取当前正在使用的标准 VIDIOC_S_STD | 设置视频标准 VIDIOC_QUERYSTD | 有的设备支持自动侦测输入源的视频标准,此时使用此ioctl查询侦测到的视频标准 5. input/output: .. csv-table:: **Input / Output** :header: ID,描述 :widths: 20,150 :quote: $ :delim: | VIDIOC_ENUMINPUT | 枚举所有input端口 VIDIOC_G_INPUT | 获取当前正在使用的input端口 VIDIOC_S_INPUT | 设置将要使用的input端口 VIDIOC_ENUMOUTPUT | 枚举所有output端口 VIDIOC_G_OUTPUT | 获取当前正在使用的output端口 VIDIOC_S_OUTPUT | 设置将要使用的output端口 VIDIOC_ENUMAUDIO | 枚举所有audio input端口 VIDIOC_G_AUDIO | 获取当前正在使用的audio input端口 VIDIOC_S_AUDIO | 设置将要使用的audio input端口 VIDIOC_ENUMAUDOUT | 枚举所有audio output端口 VIDIOC_G_AUDOUT | 获取当前正在使用的audio output端口 VIDIOC_S_AUDOUT | 设置将要使用的audio output端口 6. controls:设备特定的控制,例如设置对比度,亮度 .. csv-table:: **controls** :header: ID,描述 :widths: 20,150 :quote: $ :delim: | VIDIOC_QUERYCTRL | 查询指定control的详细信息 VIDIOC_G_CTRL | 获取指定control的值 VIDIOC_S_CTRL | 设置指定control的值 VIDIOC_G_EXT_CTRLS | 获取多个control的值 VIDIOC_S_EXT_CTRLS | 设置多个control的值 VIDIOC_TRY_EXT_CTRLS | 与VIDIOC_S_EXT_CTRLS相同,但是不改变设备状态 VIDIOC_QUERYMENU | 查询menu 7. 其他杂项: .. csv-table:: **controls** :header: ID,描述 :widths: 20,150 :quote: $ :delim: | VIDIOC_G_MODULATOR | VIDIOC_S_MODULATOR | VIDIOC_G_CROP | VIDIOC_S_CROP | VIDIOC_G_SELECTION | VIDIOC_S_SELECTION | VIDIOC_CROPCAP | VIDIOC_G_ENC_INDEX | VIDIOC_ENCODER_CMD | VIDIOC_TRY_ENCODER_CMD | VIDIOC_DECODER_CMD | VIDIOC_TRY_DECODER_CMD | VIDIOC_G_PARM | VIDIOC_S_PARM | VIDIOC_G_TUNER | VIDIOC_S_TUNER | VIDIOC_G_FREQUENCY | VIDIOC_S_FREQUENCY | VIDIOC_G_SLICED_VBI_CAP | VIDIOC_LOG_STATUS | VIDIOC_DBG_G_CHIP_IDENT | VIDIOC_S_HW_FREQ_SEEK | VIDIOC_ENUM_FRAMESIZES | VIDIOC_ENUM_FRAMEINTERVALS | VIDIOC_ENUM_DV_PRESETS | VIDIOC_S_DV_PRESET | VIDIOC_G_DV_PRESET | VIDIOC_QUERY_DV_PRESET | VIDIOC_S_DV_TIMINGS | VIDIOC_G_DV_TIMINGS | VIDIOC_DQEVENT | VIDIOC_SUBSCRIBE_EVENT | VIDIOC_UNSUBSCRIBE_EVENT | VIDIOC_CREATE_BUFS | VIDIOC_PREPARE_BUF | v4l2设备的基本操作流程如下: 1. **打开设备**,例如 ``fd = open("/dev/video0",0)`` 2. **查询设备能力**. 例如: .. code-block:: c struct capability cap; ioctl(fd,VIDIOC_QUERYCAP,&cap) 3. **设置优先级(可选)**. 4. **配置设备**。包括: * 视频输入源的视频标准,VIDIOC_*_STD * 视频数据的格式 , VIDIOC_*_FMT * 视频输入端口, VIDIOC_*_INPUT * 视频输出端口,VIDIOC_*_OUTPUT * ...... 5. **启动设备开始I/O操作**。V4L2支持一下三种I/O方式: * **Read/Write**:通过调用设备节点文件的Read/Write函数,与设备交互数据。打开设备后,默认使用的是此方法。 * **Stream I/O**:流操作,只传递数据缓冲区指针,不拷贝数据。使用此方法,需要调用VIDIOC_REQBUFS ioctl来通知设备。流操作I/O有两种方式memory map和user buffer。(具体区别后面章节介绍) * **overlay** : 也可以理解为memory to memory 传输。将数据从内存拷贝到显存中。overlay设备独有的。 对于Capture device可以以如下方式启动设备: * 调用VIDIOC_REQBUFS ioctl来分配缓冲区队列; * 调用VIDIOC_STREAMON ioctl通知设备开始stream IO * 调用VIDIOC_QBUF ioctl从设备获取一帧视频数据; * 使用完数据后,调用VIDIOC_DQBUF将缓冲区还给设备,以便设备填充下一帧数据。 6. **释放资源并关闭设备。** 1.3、从驱动层看V4L2 +++++++++++++++++++++++++ 在驱动层,V4L2为驱动编写者做了很多工作。只需要实现硬件相关的代码,并且注册相关设备即可。 硬件相关代码的编写,除了编写具体硬件的控制代码外,最主要的就是将代码与V4L2框架绑定。绑定主要分为以下两个部分: * 关系绑定:也就是要将我们自己的结构体,与V4L2框架中相关连的结构体绑定在一起。 * iocontrol等函数绑定:将V4L2所定义的空的函数指针,与自己的函数绑定在一起。 3.1 关系绑定 ***************** 提到关系绑定,就必须介绍下V4L2几个重要结构体。 * struct video_device:主要的任务就是负责向内核注册字符设备 * struct v4l2_device:一个硬件设备可能包含多个子设备,比如一个电视卡除了有capture设备,可能还有VBI设备或者FM tunner。而v4l2_device就是所有这些设备的根节点,负责管理所有的子设备。 * struct v4l2_subdev:子设备,负责实现具体的功能。 v4l2_device,v4l2_subdev可以看作所有设备和子设备的基类。我们在编写自己的驱动时,往往需要继承这些设备基类,添加一些自己的数据成员。例如第三章要讲到的soc_camera_host结构体,就是继承v4l2_device,并添加了互斥锁、子设备列表等成员变量。 .. _fig-v4l2intro: .. figure:: images/v4l2-intro.png :width: 20.0cm v4l2 framework 简略版 **绑定的基本流程** * 根据需要"重载"v4l2_device或v4l2_subdev结构体,添加需要的结构体成员。例如 : * linux/include/media/soc_camera.h文件中soc_camera_host重载了v4l2_device: .. code-block:: c :emphasize-lines: 2 struct soc_camera_host { struct v4l2_device v4l2_dev; struct list_head list; struct mutex host_lock; /* Protect during probing */ unsigned char nr; /* Host number */ void *priv; const char *drv_name; struct soc_camera_host_ops *ops; }; * linux/drivers/media/video/Ml86v7667.c中ml86v7667_priv结构体"重载"了v4l2_subdev: .. code-block:: c :emphasize-lines: 2 struct ml86v7667_priv { struct v4l2_subdev sd; struct v4l2_ctrl_handler hdl; v4l2_std_id std; }; * v4l2_device与V4L2框架的绑定:通过调用v4l2_device_register函数实现。例如,上面提到的soc_camera_host的绑定: .. code-block:: c :emphasize-lines: 41 int soc_camera_host_register(struct soc_camera_host *ici) { struct soc_camera_host *ix; int ret; if (!ici || !ici->ops || !ici->ops->try_fmt || !ici->ops->set_fmt || !ici->ops->set_bus_param || !ici->ops->querycap || ((!ici->ops->init_videobuf || !ici->ops->reqbufs) && !ici->ops->init_videobuf2) || !ici->ops->add || !ici->ops->remove || !ici->ops->poll || !ici->v4l2_dev.dev) return -EINVAL; if (!ici->ops->set_crop) ici->ops->set_crop = default_s_crop; if (!ici->ops->get_crop) ici->ops->get_crop = default_g_crop; if (!ici->ops->cropcap) ici->ops->cropcap = default_cropcap; if (!ici->ops->set_parm) ici->ops->set_parm = default_s_parm; if (!ici->ops->get_parm) ici->ops->get_parm = default_g_parm; if (!ici->ops->enum_fsizes) ici->ops->enum_fsizes = default_enum_fsizes; mutex_lock(&list_lock); list_for_each_entry(ix, &hosts, list) { if (ix->nr == ici->nr) { ret = -EBUSY; goto edevreg; } } ret = v4l2_device_register(ici->v4l2_dev.dev, &ici->v4l2_dev); if (ret < 0) goto edevreg; list_add_tail(&ici->list, &hosts); mutex_unlock(&list_lock); mutex_init(&ici->host_lock); scan_add_host(ici); return 0; edevreg: mutex_unlock(&list_lock); return ret; } * v4l2_subdev与v4l2_device的绑定:通过v4l2_device_register_subdev函数,将subdev注册到根节点上。例如: .. literalinclude:: codes/soc_camera_platform_prob.c :language: c :emphasize-lines: 31-35 * video_device与v4l2_device的绑定:将v4l2_device的地址赋值给video_device的v4l2_dev即可。 .. ttip:: 此步不一定必要。只要有办法通过文件节点file(struct file)找到v4l2_device即可。 3.2 函数绑定 ***************** 在\ :ref:`fig-v4l2intro`\ 图中,绿色的方框都是需要我们绑定并实现的。 其中v4l2_file_operations和v4l2_ioctl_ops是必须实现的。而v4l2_subdev_ops下的八类ops中,v4l2_subdev_core_ops是必须实现的,其余需要根据设备类型选择实现的。比如video capture类设备需要实现v4l2_subdev_core_ops, v4l2_subdev_video_ops。 * v4l2_file_operations:实现文件类操作,比如open,close,read,write,mmap等。但是ioctl是不需要实现的,一般都是用video_ioctl2代替。例如linux/drivers/media/video/soc_camera.c文件中soc_camera_fops的实现: .. code-block:: c :emphasize-lines: 5 static struct v4l2_file_operations soc_camera_fops = { .owner = THIS_MODULE, .open = soc_camera_open, .release = soc_camera_close, .unlocked_ioctl = video_ioctl2, .read = soc_camera_read, .mmap = soc_camera_mmap, .poll = soc_camera_poll, }; * v4l2_ioctl_ops:V4L2导出给应用层使用的所有ioctl都是在这个地方实现的。但不必全部实现,只实现自己相关的ioctl即可。例如linux/drivers/media/video/soc_camera.c中soc_camera_ioctl_ops的实现: .. code-block:: c static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = { .vidioc_querycap = soc_camera_querycap, .vidioc_try_fmt_vid_cap = soc_camera_try_fmt_vid_cap, .vidioc_g_fmt_vid_cap = soc_camera_g_fmt_vid_cap, .vidioc_s_fmt_vid_cap = soc_camera_s_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = soc_camera_enum_fmt_vid_cap, .vidioc_enum_input = soc_camera_enum_input, .vidioc_g_input = soc_camera_g_input, .vidioc_s_input = soc_camera_s_input, .vidioc_s_std = soc_camera_s_std, .vidioc_g_std = soc_camera_g_std, .vidioc_enum_framesizes = soc_camera_enum_fsizes, .vidioc_reqbufs = soc_camera_reqbufs, .vidioc_querybuf = soc_camera_querybuf, .vidioc_qbuf = soc_camera_qbuf, .vidioc_dqbuf = soc_camera_dqbuf, .vidioc_create_bufs = soc_camera_create_bufs, .vidioc_prepare_buf = soc_camera_prepare_buf, .vidioc_streamon = soc_camera_streamon, .vidioc_streamoff = soc_camera_streamoff, .vidioc_cropcap = soc_camera_cropcap, .vidioc_g_crop = soc_camera_g_crop, .vidioc_s_crop = soc_camera_s_crop, .vidioc_g_parm = soc_camera_g_parm, .vidioc_s_parm = soc_camera_s_parm, .vidioc_g_chip_ident = soc_camera_g_chip_ident, #ifdef CONFIG_VIDEO_ADV_DEBUG .vidioc_g_register = soc_camera_g_register, .vidioc_s_register = soc_camera_s_register, #endif }; * v4l2_subdev_ops:v4l2_subdev有可能需要实现的ops的总合。分为8类,core,audio,video,vbi,tuner......等。例如, linux\drivers\media\video\soc_camera_platform.c中platform_subdev_ops的实现 .. code-block:: c static struct v4l2_subdev_video_ops platform_subdev_video_ops = { .s_stream = soc_camera_platform_s_stream, .enum_mbus_fmt = soc_camera_platform_enum_fmt, .cropcap = soc_camera_platform_cropcap, .g_crop = soc_camera_platform_g_crop, .try_mbus_fmt = soc_camera_platform_fill_fmt, .g_mbus_fmt = soc_camera_platform_fill_fmt, .s_mbus_fmt = soc_camera_platform_fill_fmt, .g_mbus_config = soc_camera_platform_g_mbus_config, }; static struct v4l2_subdev_ops platform_subdev_ops = { .core = &platform_subdev_core_ops, .video = &platform_subdev_video_ops, }; 函数绑定只是将驱动所实现的函数赋值给相关的变量即可。